package com.jinanzhuan.sqlciphertest;

import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.core.util.Pair;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;

import static android.util.Base64.decode;

public class EncryptionUtils {
    private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
    private static final String ALIAS = "alias_hello";
    private Pair<String, byte[]> encryptionPair;

    @RequiresApi(api = Build.VERSION_CODES.M)
    public String encryptText(String plainText) {
        encryptionPair = encrypt(ALGORITHM, getEncryptKeyFromStore(ALIAS), plainText.getBytes());
        if (encryptionPair == null) {
            return null;
        }
        Log.e("TAG", "encrypted text: "+ encryptionPair.first);
        return encryptionPair.first;
    }

    private Pair<String, byte[]> encrypt(String algorithm, Key key, byte[] plainText) {
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encryptedBytes = cipher.doFinal(plainText);
            byte[] iv = cipher.getIV();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1 + iv.length + encryptedBytes.length);
            byteBuffer.put((byte) iv.length);
            byteBuffer.put(iv);
            byteBuffer.put(encryptedBytes);
            Log.e("TAG", "encrypt: iv: "+Arrays.toString(iv));
            Log.e("TAG", "encrypt: cipherText: "+Arrays.toString(encryptedBytes));
            Log.e("TAG", "encrypt: key: "+key.getAlgorithm()+" class: "+key);
            Log.e("TAG", "encrypt: byteBuffer: "+ Arrays.toString(byteBuffer.array()));
            return new Pair<>(b64Encode(byteBuffer.array(), 1), cipher.getIV());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
        return null;
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public byte[] decryptText(String data) {
        byte[] b64Out = b64Decode(data, 1);
        Log.e("TAG", "decrypt: data from sp: "+Arrays.toString(b64Out));
        ByteBuffer byteBuffer = ByteBuffer.wrap(b64Out);
        int ivLength = byteBuffer.get();
        byte[] iv = new byte[ivLength];
        byteBuffer.get(iv);
        Log.e("TAG", "decrypt: iv: "+Arrays.toString(iv));

        byte[] cipherText = new byte[byteBuffer.remaining()];
        byteBuffer.get(cipherText);

        IvParameterSpec ivd = new IvParameterSpec(iv);
        return decrypt(ALGORITHM, getEncryptKeyFromStore(ALIAS), ivd, cipherText);
    }

    private byte[] decrypt(String algorithm, Key key, IvParameterSpec iv, byte[] cipherText) {
        try {
            Log.e("TAG", "decrypt: cipherText: "+Arrays.toString(cipherText));
            Log.e("TAG", "decrypt: key: "+key.getAlgorithm()+" class: "+key);
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            return cipher.doFinal(cipherText);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
        return null;
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private Key getEncryptKeyFromStore(String alias) {
        try {
            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
            ks.load(null);
            if (ks.containsAlias(alias)) {
                return ks.getKey(alias, null);
            }
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
                    "AndroidKeyStore");
            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .setKeySize(256)
                    .build();
            keyGenerator.init(spec);
            return keyGenerator.generateKey();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (java.security.cert.CertificateException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String byteArrayToHexStr(byte[] byteArray) {
        if (byteArray == null) {
            return null;
        }
        char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[byteArray.length * 2];
        for (int j = 0; j < byteArray.length; j++) {
            int v = byteArray[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    public static String b64Encode(byte[] data, int b64Flag) {
        return Base64.encodeToString(data, 0, data.length, b64Flag == B64_ENCODE_FLAG.ONESDK_B64_NO_WRAP.ordinal() ? Base64.NO_WRAP : Base64.DEFAULT);
    }

    public static byte[] b64Decode(String encryptedMsg) {
        return decode(encryptedMsg, Base64.DEFAULT);
    }

    public static byte[] b64Decode(String encryptedMsg, int b64Flag) {
        return decode(encryptedMsg, b64Flag == B64_ENCODE_FLAG.ONESDK_B64_NO_WRAP.ordinal() ? Base64.NO_WRAP : Base64.DEFAULT);
    }

    enum B64_ENCODE_FLAG {
        ONESDK_B64_DEFAULT,
        ONESDK_B64_NO_WRAP
    }
}